#include "thread_pool.h"

ThreadUnit::ThreadUnit(const std::string &name): m_name(name) {
    m_thread_is_running = true;
    std::function<void(void)> f = std::bind(&ThreadUnit::Loop, this);
    m_thread = std::thread(f);
}
ThreadUnit::~ThreadUnit() {
    m_thread_is_running = false;
    {
        std::unique_lock<std::mutex> lock(m_mutex);
        m_cond.notify_all();
        while (!m_task.empty()) {
            m_task.pop();
        }
    }

    if (m_thread.joinable()) {
        m_thread.join();
    }
}

void ThreadUnit::AddTask(Task task) {
    std::unique_lock<std::mutex> lock(m_mutex);
    m_task.push(task);
    m_cond.notify_all();
}
void ThreadUnit::Loop() {
    while (m_thread_is_running) {
        std::unique_lock<std::mutex> lock(m_mutex);
        std::cout << m_name << ": wait......" << std::endl;
        while(m_task.empty() && m_thread_is_running) {
            m_cond.wait(lock);
        }
        std::cout << m_name << " m_task size = " << m_task.size() << std::endl;
        if (m_thread_is_running == false) {
            break;
        }
        Task task = m_task.front();
        m_task.pop();
        lock.unlock();

        //do task
        // std::cout << "thread id: " << std::this_thread::get_id() << std::endl;
        task.fun(task.arg);

        lock.lock();
    }
}

size_t ThreadUnit::GetTaskNum() {
    std::unique_lock<std::mutex> lock(m_mutex);
    return m_task.size();
}

const std::string &ThreadUnit::GetThreadName() {
    return m_name;
}

ThreadPool::ThreadPool(size_t num) {
    if (num < 1) {
        std::cout << "thread num must >= 1" << std::endl;
        return;
    }
    m_thread_num = num;
    m_thread_is_running = true;
    for (size_t i = 0; i < m_thread_num; i++) {
        m_thread_unit.push_back(new ThreadUnit(
                std::string("thread") + std::to_string(i)));
    }
}
ThreadPool::~ThreadPool() {
    m_thread_is_running = false;
    for (auto i : m_thread_unit) {
        delete i;
    }
}

size_t ThreadPool::SelectThread() {
    size_t index = 0;
    size_t min_thread_task = m_thread_unit[0]->GetTaskNum();
    for (size_t i = 0; i < m_thread_unit.size(); i++) {
        if (m_thread_unit[i]->GetTaskNum() < min_thread_task) {
            min_thread_task = m_thread_unit[i]->GetTaskNum();
            index = i;
        }
    }
    std::cout << m_thread_unit[index]->GetThreadName()
              << " has task size is " << min_thread_task
              << ", is lightly loaded, add task."
              << std::endl;
    return index;
}

void ThreadPool::SubmitTask(ThreadUnit::Task task) {
    m_thread_unit[SelectThread()]->AddTask(task);
}
